package com.herionthenet;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class SocketClientHandler {
	
	private ByteBuffer socketBuffer;
	private BlockingQueue<ByteBuffer> bufferQueue;
	
	private SocketChannel sockchannel;
	private boolean connectionClosed = false;
	private SocketClientAdapter adapter;
	
	public SocketClientHandler(SocketClientAdapter adapter, SocketChannel client) {
		this.adapter = adapter;
		this.sockchannel = client;
		this.socketBuffer = ByteBuffer.allocate(64 * 1024);
		socketBuffer.flip();
		
		this.bufferQueue = new LinkedBlockingQueue<ByteBuffer>(10);		
	}
	
	public void handleRead() throws IOException{		
		
		if(!socketBuffer.hasRemaining()){
			socketBuffer.rewind();
			socketBuffer.limit(socketBuffer.capacity());
			
			if(sockchannel.read(socketBuffer) == -1)
				close();
			
			socketBuffer.flip();
		}
		
		
		if(socketBuffer.hasRemaining()){
			List<byte[]> frames = adapter.getFrameDecoder().decode(socketBuffer);
			for(byte[] f : frames){
				switch (adapter.getFrameDecoder().getFramerDataOption()) {
				case TEXT:
					adapter.onMessage(new String(f));
					break;
				case BINARY:
					adapter.onMessage(f);
					break;
				default:					
					throw new IllegalArgumentException("Frame payload is not yet decided");
				}
				
			}			
		}
	}
	
	public void send(Object object) throws InterruptedException {
		if(adapter.getFrameEncoder() != null)
			send(adapter.getFrameEncoder().encode(object));
		else if(object instanceof String)
			send((String)object);
		else if(object instanceof ByteBuffer)
			send(object);
		else
			throw new IllegalArgumentException("Invalid incoming data");
			
	}
	
	private void send(String text) throws InterruptedException{
		ByteBuffer buffer = ByteBuffer.allocate(text.length());
		buffer.put(text.getBytes());
		send(buffer);
	}
	
	private void send(ByteBuffer buffer) throws InterruptedException{
		buffer.rewind();
		bufferQueue.put(buffer);
		adapter.onWriteDemmand();
	}
	
	public void flush() throws IOException{
		ByteBuffer buffer = this.bufferQueue.peek();
		while(buffer != null){
			sockchannel.write(buffer);
			if(buffer.remaining() > 0)
				continue;
			else{
				this.bufferQueue.poll(); // Remove buffer
				buffer = this.bufferQueue.peek();
			}
		}
	}
	
	public boolean isClosed(){
		return connectionClosed;
	}
	
	public void close(){
		closeConnection();
	}
	
	public void closeConnection() {
		if( connectionClosed ) {
			return;
		}
		connectionClosed = true;
		
		try {
			sockchannel.close();
			adapter.onClose(null);
		} catch (IOException e) {
			adapter.onClose(e.getMessage());
			adapter.onError(e);
		}		
	}
}
